#ifndef __SOCKET_H__
#define __SOCKET_H__

#include "Spark/Utils/NonCopyable.h"
#include "Spark/Net/SocketAddr.h"
#include <netinet/tcp.h>
#include <system_error>
#include <sys/socket.h>

/**
 * @brief Socket Options
 */
enum class SO_ : int
{
    REUSEPORT = SO_REUSEPORT,
    REUSEADDR = SO_REUSEADDR,
    KEEPALIVE = SO_KEEPALIVE,
    LINGER = SO_LINGER,
    SNDBUF = SO_SNDBUF,
    RCVBUF = SO_RCVBUF
};

class Socket : public NonCopyable
{
  public:
    explicit Socket(int fd) : sockfd_(fd)
    {
    }
    ~Socket();

  public:
    static int createSocket(sa_family_t family);

    static SocketAddr getLocalAddr(int sockfd);

    static SocketAddr getPeerAddr(int sockfd);

    /**
     * @brief Get the Socket Errno for given fd
     * @param  fd Given socket fd
     * @return int
     */
    static int getSockErrno(int fd);

    static bool isSelfConnected(int fd);

    bool isSelfConnected()
    {
        return getLocalAddr(sockfd_) == getPeerAddr(sockfd_);
    }

    int fd() const
    {
        return sockfd_;
    }

    /**
     * @brief Get the Socket Errno for this socket
     * @return int
     */
    int getSockErrno();

    bool getTcpInfo(struct tcp_info* tcpi) const;

    bool getTcpInfoString(char* buf, int len) const;

    /**
     * @brief Bind current sockfd to given addr
     * @param  addr Addr to bind
     */
    void bind(SocketAddr* addr);

    /**
     * @brief Let the current sockfd listen for incoming conections
     */
    void listen();

    /**
     * @brief Connect current socket fd to target peeraddr
     * @param  peeraddr Pointer to target peer
     */
    void connect(SocketAddr* peeraddr);

    static int connect(int sockfd, SocketAddr* peeraddr);

    /**
     * @brief Accept connection
     * @return New client socket fd
     */
    int accept(SocketAddr* peeraddr);

    /**
     * @brief Call shutdown() to close write
     */
    void closeWrite();

    /**
     * @brief Set the Socket Options
     * @param  option The SO option to set
     * @param  optval The value for option, 1 for turnning on, 0 for turning off
     */
    void setSocketOption(SO_ option, socklen_t optval);

    void setTCPNoDelay(socklen_t optval);

    /**
     * @brief Read n bytes of data and write to given buffer
     * @param  buffer Destination buffer
     * @param  len N bytes to read
     * @return ssize_t Bytes actually read or -1 if error
     */
    ssize_t readn(char* buffer, size_t len);

  protected:
    int sockfd_;
};

#endif  // __SOCKET_H__